Plot themes

Customization is a crucial step to achieve appealing visual representations. As you have most likely already noticed, ggplot has its own style. That is, you can tell apart a basic ggplot from any other kind of representation. Luckily for us, we don’t have to stick to the standard theme. We can customize almost anything, though that implies becoming familiar with a large number of instructions. By now, we will show you alternative ggplot themes that have been predetermined and may suffice to remove the ugliness of our plots so far.

ggplot2 offers several pre-set styling to rapidly amend the uglyness of the default settings.## Complete themes {#themes}

ggplot2 comes with a number of built in themes. The most important is theme_grey(), the signature ggplot2 theme with a light grey background and white gridlines. The theme is designed to put the data forward while supporting comparisons. We can still see the gridlines to aid in the judgement of position, but they have little visual impact and we can easily ‘tune’ them out. The grey background gives the plot a similar typographic colour to the text, ensuring that the graphics fit in with the flow of a document without jumping out with a bright white background. Finally, the grey background creates a continuous field of colour which ensures that the plot is perceived as a single visual entity.

  • theme_bw(): a variation on theme_grey() that uses a white background and thin grey grid lines.

  • theme_linedraw(): A theme with only black lines of various widths on white backgrounds, reminiscent of a line drawing.

  • theme_light(): similar to theme_linedraw() but with light grey lines and axes, to direct more attention towards the data.

  • theme_dark(): the dark cousin of theme_light(), with similar line sizes but a dark background. Useful to make thin coloured lines pop out.

  • theme_minimal(): A minimalistic theme with no background annotations.

  • theme_classic(): A classic-looking theme, with x and y axis lines and no gridlines.

  • theme_void(): A completely empty theme.

Each one has it’s own style and it’s up to you to decide which one is closer to your needs. Don’t worry, even after assigning a theme we can keep customizing. In fact, these default themes are good starting point towards our own themes. Just invoke one of them at the end of the ggplot call and we are done.

fires %>%
    group_by(CAUSE) %>%
    ggplot() +
        geom_density(data=dplyr::select(fires,-CAUSE), aes(x=log(BAREA)),fill='lightgrey', color =NA) + #all fires without MONTH
        geom_density(aes(x=log(BAREA)), color ='red') +
        facet_wrap(~CAUSE) +
    theme_bw()

Getting additional themes

Same as color scales, several developers invested some time to create specific themes. The package ggthemes is a good example. It includes a collection of predifined themes mimicking the style of some of the most famous journals.

You can take a quick tour in https://yutannihilation.github.io/allYourFigureAreBelongToUs/ggthemes/.

Setting up our own theme

Despite of the wide array of themes that we can get, it is normally the case we want to set up our own theme. That is particularly important when we are involved in a scientific document and we need to produce similar figures in terms of style.

To do so we can create our theme, and then call it in ggplot same as any other. The following chunk of code provides an example. Rather than starting from the scratch we take the theme_bw as basis and then customize it.

newtheme <- theme_bw() +
  theme(panel.grid.major.x = element_blank(),
        panel.grid.minor.x = element_blank(),
        panel.background = element_rect(color='white'),
        axis.text.x = element_text(face = "bold", 
                                   size = 7, 
                                   vjust = 0.5,
                                   angle = 0))

Once we got the theme we just add it to a plot:

library(tidytext)

#Getting data for Cantabria alone
d<- fires %>%
    filter(CCAA == 'Cantabria') %>%
    group_by(CCAA) %>%
    summarise(N=n(), BA = sum(BAREA)) %>%
    pivot_longer(cols = c(N:BA), names_to = 'var' ,values_to = 'value') %>%
    ungroup() %>%
    #reorder_within allows sorting per group
    mutate(CCAA = reorder_within(CCAA,value,var))

#Getting data for all CCAA and plotting
fires %>%
    group_by(CCAA) %>%
    summarise(N=n(), BA = sum(BAREA)) %>%
    pivot_longer(cols = c(N:BA), names_to = 'var' ,values_to = 'value') %>%
    ungroup() %>%
    mutate(CCAA = reorder_within(CCAA,value,var)) %>%
    ggplot() +
        geom_col(aes(y=reorder(CCAA,value),x=round(value,0),group = var), fill = 'steelblue', width = 0.7) +
        geom_col(data=d,aes(y=reorder(CCAA,value),x=round(value,0),group = var), fill = 'coral3', width = 0.7) +
        scale_y_reordered(name = 'CCAA') +
        labs(x='') +
        facet_wrap(~var, scales = 'free') +
        newtheme

Note that when calling our own theme we do not use parenthesis.

So far so good. But, how can we manipulate the elements of a plot. To modify an individual theme component you use code like plot + theme(element.name = element_function()). In this section you’ll learn about the basic element functions, and then in the next section, you’ll see all the elements that you can modify.

There are four basic types of built-in element functions: text, lines, rectangles, and blank. Each element function has a set of parameters that control the appearance:

  • element_text() draws labels and headings. You can control the font family, face, colour, size (in points), hjust, vjust, angle (in degrees) and lineheight (as ratio of fontcase). More details on the parameters can be found in vignette("ggplot2-specs").
    base <- ggplot(fires,aes(x=log(BAREA))) +
                    geom_histogram()
    base_t <- base + labs(title = "This is a ggplot") + xlab(NULL) + ylab(NULL)
    base_t + theme(plot.title = element_text(size = 16))

    base_t + theme(plot.title = element_text(face = "bold", colour = "red"))

    base_t + theme(plot.title = element_text(hjust = 1))

You can control the margins around the text with the margin argument andmargin() function. margin() has four arguments: the amount of space (in points) to add to the top, right, bottom and left sides of the text. Any elements not specified default to 0.

    # The margins here look asymmetric because there are also plot margins
    base_t + theme(plot.title = element_text(margin = margin()))

    base_t + theme(plot.title = element_text(margin = margin(t = 10, b = 10)))

    base_t + theme(axis.title.y = element_text(margin = margin(r = 10)))

  • element_line() draws lines parameterised by colour, size and linetype:
    base + theme(panel.grid.major = element_line(colour = "black"))

    base + theme(panel.grid.major = element_line(size = 2))

    base + theme(panel.grid.major = element_line(linetype = "dotted"))

  • element_rect() draws rectangles, mostly used for backgrounds, parameterised by fill colour and border colour, size and linetype.
    base + theme(plot.background = element_rect(fill = "grey80", colour = NA))

    base + theme(plot.background = element_rect(colour = "red", size = 2))

    base + theme(panel.background = element_rect(fill = "linen"))

  • element_blank() draws nothing. Use this if you don’t want anything drawn, and no space allocated for that element. The following example uses element_blank() to progressively suppress the appearance of elements we’re not interested in. Notice how the plot automatically reclaims the space previously used by these elements: if you don’t want this to happen (perhaps because they need to line up with other plots on the page), use colour = NA, fill = NA to create invisible elements that still take up space.
    base

    last_plot() + theme(panel.grid.minor = element_blank())

    last_plot() + theme(panel.grid.major = element_blank())

    last_plot() + theme(panel.background = element_blank())

    last_plot() + theme(
      axis.title.x = element_blank(), 
      axis.title.y = element_blank()
    )

    last_plot() + theme(axis.line = element_line(colour = "grey50"))

  • A few other settings take grid units. Create them with unit(1, "cm") or unit(0.25, "in").

To modify theme elements for all future plots, use theme_update(). It returns the previous theme settings, so you can easily restore the original parameters once you’re done.

old_theme <- theme_update(
  plot.background = element_rect(fill = "lightblue3", colour = NA),
  panel.background = element_rect(fill = "lightblue", colour = NA),
  axis.text = element_text(colour = "linen"),
  axis.title = element_text(colour = "linen")
)
base

theme_set(old_theme)
base

Theme elements

There are around 40 unique elements that control the appearance of the plot. They can be roughly grouped into five categories: plot, axis, legend, panel and facet. The following sections describe each in turn.

Plot elements

Some elements affect the plot as a whole:

Element Setter Description
plot.background element_rect() plot background
plot.title element_text() plot title
plot.margin margin() margins around plot

plot.background draws a rectangle that underlies everything else on the plot. By default, ggplot2 uses a white background which ensures that the plot is usable wherever it might end up (e.g. even if you save as a png and put on a slide with a black background). When exporting plots to use in other systems, you might want to make the background transparent with fill = NA. Similarly, if you’re embedding a plot in a system that already has margins you might want to eliminate the built-in margins. Note that a small margin is still necessary if you want to draw a border around the plot.

base + theme(plot.background = element_rect(colour = "grey50", size = 2))

base + theme(
  plot.background = element_rect(colour = "grey50", size = 2),
  plot.margin = margin(2, 2, 2, 2)
)

base + theme(plot.background = element_rect(fill = "lightblue"))

Axis elements

The axis elements control the apperance of the axes:

Element Setter Description
axis.line element_line() line parallel to axis (hidden in default themes)
axis.text element_text() tick labels
axis.text.x element_text() x-axis tick labels
axis.text.y element_text() y-axis tick labels
axis.title element_text() axis titles
axis.title.x element_text() x-axis title
axis.title.y element_text() y-axis title
axis.ticks element_line() axis tick marks
axis.ticks.length unit() length of tick marks

Note that axis.text (and axis.title) comes in three forms: axis.text, axis.text.x, and axis.text.y. Use the first form if you want to modify the properties of both axes at once: any properties that you don’t explicitly set in axis.text.x and axis.text.y will be inherited from axis.text.

df <- data.frame(x = 1:3, y = 1:3)
base <- ggplot(df, aes(x, y)) + geom_point()
# Accentuate the axes
base + theme(axis.line = element_line(colour = "grey50", size = 1))

# Style both x and y axis labels
base + theme(axis.text = element_text(color = "blue", size = 12))

# Useful for long labels
base + theme(axis.text.x = element_text(angle = -90, vjust = 0.5))

The most common adjustment is to rotate the x-axis labels to avoid long overlapping labels. If you do this, note negative angles tend to look best and you should set hjust = 0 and vjust = 1:

df <- data.frame(
  x = c("label", "a long label", "an even longer label"), 
  y = 1:3
)
base <- ggplot(df, aes(x, y)) + geom_point()
base

base + 
  theme(axis.text.x = element_text(angle = -30, vjust = 1, hjust = 0)) + 
  xlab(NULL) + 
  ylab(NULL)

Legend elements

The legend elements control the apperance of all legends. You can also modify the appearance of individual legends by modifying the same elements in guide_legend() or guide_colourbar().

Element Setter Description
legend.background element_rect() legend background
legend.key element_rect() background of legend keys
legend.key.size unit() legend key size
legend.key.height unit() legend key height
legend.key.width unit() legend key width
legend.margin unit() legend margin
legend.text element_text() legend labels
legend.text.align 0–1 legend label alignment (0 = right, 1 = left)
legend.title element_text() legend name
legend.title.align 0–1 legend name alignment (0 = right, 1 = left)

These options are illustrated below:

df <- data.frame(x = 1:4, y = 1:4, z = rep(c("a", "b"), each = 2))
base <- ggplot(df, aes(x, y, colour = z)) + geom_point()
base + theme(
  legend.background = element_rect(
    fill = "lemonchiffon", 
    colour = "grey50", 
    size = 1
  )
)

base + theme(
  legend.key = element_rect(color = "grey50"),
  legend.key.width = unit(0.9, "cm"),
  legend.key.height = unit(0.75, "cm")
)

base + theme(
  legend.text = element_text(size = 15),
  legend.title = element_text(size = 15, face = "bold")
)

There are four other properties that control how legends are laid out in the context of the plot (legend.position, legend.direction, legend.justification, legend.box). They are described in Section @ref(legend-layout).

Panel elements

Panel elements control the appearance of the plotting panels:

Element Setter Description
panel.background element_rect() panel background (under data)
panel.border element_rect() panel border (over data)
panel.grid.major element_line() major grid lines
panel.grid.major.x element_line() vertical major grid lines
panel.grid.major.y element_line() horizontal major grid lines
panel.grid.minor element_line() minor grid lines
panel.grid.minor.x element_line() vertical minor grid lines
panel.grid.minor.y element_line() horizontal minor grid lines
aspect.ratio numeric plot aspect ratio

The main difference between panel.background and panel.border is that the background is drawn underneath the data, and the border is drawn on top of it. For that reason, you’ll always need to assign fill = NA when overriding panel.border.

base <- ggplot(df, aes(x, y)) + geom_point()
# Modify background
base + theme(panel.background = element_rect(fill = "lightblue"))

# Tweak major grid lines
base + theme(
  panel.grid.major = element_line(color = "gray60", size = 0.8)
)

# Just in one direction  
base + theme(
  panel.grid.major.x = element_line(color = "gray60", size = 0.8)
)

Note that aspect ratio controls the aspect ratio of the panel, not the overall plot:

base2 <- base + theme(plot.background = element_rect(colour = "grey50"))
# Wide screen
base2 + theme(aspect.ratio = 9 / 16)

# Long and skiny
base2 + theme(aspect.ratio = 2 / 1)

# Square
base2 + theme(aspect.ratio = 1)